/* * Copyright (c) 2017 Tatsuya Maki * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.t28.json2java.idea; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.name.Named; import com.intellij.ide.IdeView; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataKeys; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.application.RunResult; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.util.PlatformIcons; import io.t28.json2java.idea.command.CommandActionFactory; import io.t28.json2java.idea.command.NewClassCommandAction; import io.t28.json2java.idea.exception.ClassAlreadyExistsException; import io.t28.json2java.idea.exception.InvalidDirectoryException; import io.t28.json2java.idea.inject.GuiceManager; import io.t28.json2java.idea.inject.JavaConverterFactory; import io.t28.json2java.idea.setting.Json2JavaSettings; import io.t28.json2java.idea.util.Formatter; import io.t28.json2java.idea.view.NewClassDialog; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Optional; import java.util.stream.Stream; public class NewClassAction extends AnAction implements NewClassDialog.ActionListener { private static final Logger LOGGER = Logger.getInstance(NewClassAction.class); private static final String NOTIFICATION_DISPLAY_ID = "Json2Java4Idea"; private Project project; private IdeView ideView; @Inject @SuppressWarnings("unused") private Json2JavaBundle bundle; @Inject @SuppressWarnings("unused") private Json2JavaSettings settings; @Inject @Named("Name") @SuppressWarnings("unused") private Provider<InputValidator> nameValidatorProvider; @Inject @Named("Json") @SuppressWarnings("unused") private Provider<InputValidator> jsonValidatorProvider; @Inject @SuppressWarnings("unused") private Provider<CommandActionFactory> commandActionFactoryProvider; @Inject @SuppressWarnings("unused") private Provider<JavaConverterFactory> javaConverterFactoryProvider; @Inject @Named("Json") @SuppressWarnings("unused") private Provider<Formatter> jsonFormatterProvider; public NewClassAction() { super(PlatformIcons.CLASS_ICON); } @Override public void actionPerformed(@Nonnull AnActionEvent event) { if (!isAvailable(event)) { return; } project = event.getProject(); ideView = event.getData(DataKeys.IDE_VIEW); final Injector injector = GuiceManager.getInstance(project).getInjector(); injector.injectMembers(this); // 'selected' is null when directory selection is canceled although multiple directories are chosen. final PsiDirectory selected = ideView.getOrChooseDirectory(); if (selected == null) { return; } final NewClassDialog dialog = NewClassDialog.builder(project, bundle) .nameValidator(nameValidatorProvider.get()) .jsonValidator(jsonValidatorProvider.get()) .actionListener(this) .build(); dialog.show(); } @Override public void update(@Nonnull AnActionEvent event) { final boolean isAvailable = isAvailable(event); final Presentation presentation = event.getPresentation(); presentation.setEnabledAndVisible(isAvailable); } @Override public void onOk(@Nonnull NewClassDialog dialog) { final PsiDirectory directory = ideView.getOrChooseDirectory(); if (directory == null) { dialog.cancel(); return; } try { final CommandActionFactory actionFactory = commandActionFactoryProvider.get(); final JavaConverterFactory converterFactory = javaConverterFactoryProvider.get(); final NewClassCommandAction command = actionFactory.create( dialog.getClassName(), dialog.getJson(), directory, converterFactory.create(settings) ); final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.runProcessWithProgressSynchronously(() -> { final ProgressIndicator indicator = progressManager.getProgressIndicator(); if (indicator != null) { indicator.setIndeterminate(true); indicator.setText(bundle.message("progress.text", dialog.getClassName())); } final RunResult<PsiFile> result = command.execute(); return result.getResultObject(); }, bundle.message("progress.title"), false, project); dialog.close(); } catch (RuntimeException e) { LOGGER.warn("Unable to create a class", e); onError(dialog, e.getCause()); } } @Override public void onCancel(@Nonnull NewClassDialog dialog) { dialog.cancel(); } @Override public void onFormat(@Nonnull NewClassDialog dialog) { final Formatter formatter = jsonFormatterProvider.get(); final String formatted = formatter.format(dialog.getJson()); dialog.setJson(formatted); } @Override public void onSettings(@Nonnull NewClassDialog dialog) { ShowSettingsUtil.getInstance().showSettingsDialog(project, bundle.message("settings.name")); } private void onError(@Nonnull NewClassDialog dialog, @Nullable Throwable cause) { if (cause instanceof ClassAlreadyExistsException) { // Dialog is not closed or cancelled since user can rename class after message showing Messages.showMessageDialog( project, bundle.message("error.message.class.exists", dialog.getClassName()), bundle.message("error.title.cannot.create.class"), Messages.getErrorIcon() ); return; } if (cause instanceof InvalidDirectoryException) { final Notification notification = new Notification( NOTIFICATION_DISPLAY_ID, bundle.message("error.title.directory.invalid"), bundle.message("error.message.directory.invalid"), NotificationType.WARNING ); Notifications.Bus.notify(notification); dialog.close(); return; } final Notification notification = new Notification( NOTIFICATION_DISPLAY_ID, bundle.message("error.title.cannot.create.class"), bundle.message("error.message.cannot.create", dialog.getClassName()), NotificationType.ERROR ); Notifications.Bus.notify(notification); dialog.close(); } @CheckReturnValue @VisibleForTesting @SuppressWarnings("WeakerAccess") static boolean isAvailable(@Nonnull AnActionEvent event) { final Project project = event.getProject(); if (project == null) { return false; } final IdeView view = event.getData(LangDataKeys.IDE_VIEW); if (view == null) { return false; } final ProjectRootManager rootManager = ProjectRootManager.getInstance(project); final ProjectFileIndex fileIndex = rootManager.getFileIndex(); final Optional<PsiDirectory> sourceDirectory = Stream.of(view.getDirectories()) .filter(directory -> { final VirtualFile virtualFile = directory.getVirtualFile(); return fileIndex.isUnderSourceRootOfType(virtualFile, JavaModuleSourceRootTypes.SOURCES); }) .findFirst(); return sourceDirectory.isPresent(); } }